﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using System.IO;


namespace PixelWarfare
{    

    /// <summary>
    /// Main game class, handles drawing, input, and game state.
    /// </summary>
    class Game
    {
        #region Fields

        private static Game theOne;                 // Singleton instance.

        public static Boolean RandomMap = false;    // True when the map is chosen randomly. 
        public static int ScoringType = ByPoints;   // Scoring type. 

        public const int ByWins = 0;
        public const int ByPoints = 1;

        public const int Width = 500;
        public const int Height = 500;

        public const int MaxPlayers = 4;
        public const int TurnsPerPlayer = 5;

        private const int MaxPower = 1000;          // Total power for all players.      
        private const int MaxTurns = 20;

        private BlobField blobField;
        private List<Player> players;

        private int playerMaxPower;                 // MaxPower / number of players.
        private int currentNumPlayers;

        private int nextPlayerIndex;                // Keeps track of next player index.
        private int turnNumber;                     // Current turn number.

        private int selx, sely;                     // Coordinate of mouse when it was pressed.
        private long pressTime;                     // Time the mouse was pressed in millis.
        private RadarCircle currentRC;              // RC for the current mouse selection.

        private bool inSelection;                   // true if a valid blob location was clicked.

        private bool roundOver;

        private Bitmap currentMap;
        
        #endregion

        #region Constructors

        /// <summary>
        /// Create and initialize a new Game.
        /// </summary>
        private Game()
        {
            blobField = new BlobField(Width, Height);

            currentNumPlayers = 4;

            roundOver = false;

            players = new List<Player>();

            players.Add(new Player("RED", Color.FromArgb(0xCD,0,0), nextPlayerIndex++, playerMaxPower));
            players.Add(new Player("BLUE", Color.MediumBlue, nextPlayerIndex++, playerMaxPower));
            players.Add(new Player("PURPLE", Color.Purple, nextPlayerIndex++, playerMaxPower));
            players.Add(new Player("GREEN", Color.Green, nextPlayerIndex++, playerMaxPower));

        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the singleton instance of the class.
        /// </summary>
        /// <returns>Return the singleton instance.</returns>
        public static Game GetInstance()
        {
            if (theOne == null)
            {
                theOne = new Game();
            }

            return theOne;
        }
        
        /// <summary>
        /// Updates the game state and returns true when the game is done.
        /// </summary>
        /// <returns>Whether the game is done.</returns>
        public bool Update()
        {
            bool result = blobField.Update();

            if (!result)
            {
                roundOver = false;    
            }
            else if (result && !roundOver)
            {
                roundOver = true;

                

                if (ScoringType == ByPoints)
                {
                    for (int i = 0; i < MaxPlayers; i++)
                    {
                        players[i].score += (int)Math.Round(blobField.GetTeamPercentage(i) * 1000);
                    }
                    
                }
                else
                {
                    int winner = 0;
                    double score = -1;

                    // Find the winning player.
                    for (int i = 0; i < MaxPlayers; i++)
                    {
                        if (blobField.GetTeamPercentage(i) > score)
                        {
                            score = blobField.GetTeamPercentage(i);
                            winner = i;
                        }
                    }

                    players[winner].score++;
                }
                
            }

            return result;
        }

        /// <summary>
        /// Attempts to add the new selection to the game field.
        /// If successful will return true, otherwise false.
        /// </summary>
        /// <param name="x">X coordinate of the blob to be added.</param>
        /// <param name="y">Y coordinate of the blob to be added.</param>
        /// <returns>Whether the addition is successful.</returns>
        public bool NewSelection(int x, int y)
        {
            // Check if the point is valid, and there are still turns.
            if (SafePoint(x, y) && blobField.IsActivePoint(x, y) && turnNumber < MaxTurns)
            {
                pressTime = DateTime.Now.Ticks / 10000; // Current number of millis
                selx = x;
                sely = y;
                currentRC = new RadarCircle(x, y, playerMaxPower / 2, players[turnNumber % MaxPlayers].color);
                inSelection = true;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Ends the current selection and adds it to the field.
        /// </summary>
        public void EndSelection()
        {
            if (inSelection)
            {
                Player current = players.ElementAt(turnNumber % MaxPlayers);

                // Setup the current selection.
                Selection s = new Selection();
                s.x = selx;
                s.y = sely;

                // Assign size based on time passed since mouse was pressed.
                s.size = (int)(DateTime.Now.Ticks / 10000 - pressTime) / 5;

                // Size must be smaller then the players power left.
                s.size = Math.Min(players.ElementAt(turnNumber % MaxPlayers).powerLeft, s.size);

                //Size cannot be bigger then half the players max power.
                s.size = Math.Min(playerMaxPower / 2 + 1, s.size);

                currentRC.ChangeSize(s.size);

                s.rc = currentRC;
                current.powerLeft -= s.size;
                current.blobs.Add(s);

                // Add it to the field.
                blobField.AddBlob(s.x, s.y, 0, s.size, current.team, current.color);

                // Next players turn.
                turnNumber++;

                // If the next player has no power left or isn't active skip over him.
                while (turnNumber < MaxTurns && (!players.ElementAt(turnNumber % nextPlayerIndex).active ||
                    players.ElementAt(turnNumber % nextPlayerIndex).powerLeft == 0))
                {
                    turnNumber++;
                }

                inSelection = false;
            }
        }

        /// <summary>
        /// Returns whether their are more turns left.
        /// </summary>
        /// <returns>Whether there are more turns left.</returns>
        public bool MoreTurns()
        {
            return turnNumber < MaxTurns;
        }

        /// <summary>
        /// Reset/Init game variables before going into blob selection.
        /// </summary>
        public void InitGameVariables()
        {
            currentNumPlayers = 0;

            foreach (Player p in players)
            {
                if (p.active)
                {
                    currentNumPlayers++;
                }
            }

            // Reset the game field and remove all blobs.
            blobField.Restart();
            blobField.RemoveMap();
            blobField.FlushBlobs();

            playerMaxPower = MaxPower / currentNumPlayers;

            // Reset the before starting a new game.
            foreach (Player p in players)
            {
                p.powerLeft = playerMaxPower;
                p.blobs = new List<Selection>();
            }

            turnNumber = 0;

            while (!players[turnNumber % MaxPlayers].active)
            {
                turnNumber++;
            }
        }

        /// <summary>
        /// Get the radar circle associated with the current slection being made.
        /// </summary>
        /// <returns>The current selection radar circle.</returns>
        public RadarCircle GetCurrentSelection()
        {
            int size = (int)(DateTime.Now.Ticks / 10000 - pressTime) / 5;

            // Size must be smaller then the players power left.
            size = Math.Min(players.ElementAt(turnNumber % MaxPlayers).powerLeft, size);

            //Size cannot be bigger then half the players max power.
            size = Math.Min(playerMaxPower / 2 + 1, size);

            // Update the size before returning incase its drawn.
            currentRC.ChangeSize(size);

            return currentRC;
        }

        /// <summary>
        /// Get all the blob selection radar circles.
        /// </summary>
        /// <returns> A list of all the blobs previously selected radar circles.</returns>
        public List<RadarCircle> GetSelections() 
        {
            List<RadarCircle> result = new List<RadarCircle>();

            foreach (Player p in players)
            {
                foreach (Selection s in p.blobs)
                {
                    result.Add(s.rc);
                }
            }

            return result;
        }

        /// <summary>
        /// Returns the bitmap of the map on the field.
        /// </summary>
        /// <returns>The map bitmap.</returns>
        public Bitmap GetMap()
        {
            // If the map hasnt been set create a new completely black map.
            if (currentMap == null)
            {
                currentMap = new Bitmap(Game.Width, Game.Height);
            }

            return currentMap;
        }

        /// <summary>
        /// Set the map of the field and update the active pixels.
        /// </summary>
        /// <param name="map">The new map.</param>
        public void SetMap(Bitmap map)
        {
            currentMap = map;
            blobField.ActivateMap(map);
        }

        /// <summary>
        /// Set the activity of a given player.
        /// </summary>
        /// <param name="player">Player to change activity.</param>
        /// <param name="active">What to change the activty too.</param>
        public void SetActivity(int player, bool active)
        {
            players[player].active = active;
        }

        /// <summary>
        /// Get the activty of the given player.
        /// </summary>
        /// <param name="player">The player of the requested activty.</param>
        /// <returns>The activty.</returns>
        public bool GetActivity(int player)
        {
            return players[player].active;
        }

        /// <summary>
        /// Get the name of the winning player, or
        /// the player who filled the most of the screen.
        /// </summary>
        /// <returns>The winners name.</returns>
        public string GetWinnerName()
        {
            double highest = 0;
            Player winner = players.ElementAt(0);

            // Find out who filled the most of the screen.
            foreach (Player p in players)
            {
                if (p.active)
                {

                    if (blobField.GetTeamPercentage(p.team) > highest)
                    {
                        highest = blobField.GetTeamPercentage(p.team);
                        winner = p;
                    }
                }
            }

            return winner.name;
        }

        /// <summary>
        /// Get the name of player whose turn it is.
        /// </summary>
        /// <returns>The player name.</returns>
        public string GetCurrentPlayerName()
        {
            return players.ElementAt(turnNumber % MaxPlayers).name;
        }

        /// <summary>
        /// Get the number of turns the current player has left.
        /// </summary>
        /// <returns>The number of turns.</returns>
        public int GetCurrentPlayerTurns()
        {
            return (int)Math.Ceiling((MaxTurns - turnNumber + 0.0) / MaxPlayers);
        }

        /// <summary>
        /// Get the image of the blob field.
        /// </summary>
        /// <returns>The blob field image.</returns>
        public Bitmap GetFieldImage()
        {
            return blobField.GetImage();
        }

        /// <summary>
        /// Reset the score of each player back to 0;
        /// </summary>
        public void ResetScores()
        {
            foreach (Player p in players)
            {
                p.score = 0;
            }
        }

        /// <summary>
        /// Returns the score of the given player.
        /// </summary>
        /// <param name="player">The player whos score was requested.</param>
        /// <returns>The score.</returns>
        public int GetScore(int player)
        {
            return players[player].score;
        }

        /// <summary>
        /// Get the percent of the given players remaining power.
        /// Returns -1 if the player isnt active.
        /// </summary>
        /// <param name="player">The player whos percent was requested.</param>
        /// <returns>The percent.</returns>
        public double GetPowerPercentage(int player)
        {
            if (!players[player].active)
            {
                return -1;
            }

            else if (player == turnNumber % MaxPlayers)
            {

                int powerLeft = players[player].powerLeft;

                // If the player is making a selection subtract off the current selection.
                if (inSelection)
                {
                    // Assign size based on time passed since mouse was pressed.
                    double size = (DateTime.Now.Ticks / 10000 - pressTime) / 5;

                    // Size must be smaller then the players power left.
                    size = Math.Min(players.ElementAt(turnNumber % MaxPlayers).powerLeft, size);

                    //Size cannot be bigger then half the players max power.
                    size = Math.Min(playerMaxPower / 2 + 1, size);

                    return (powerLeft - size) / playerMaxPower;
                }
                else
                {
                    return (powerLeft + 0.0) / playerMaxPower;
                }
            }
            else
            {
                return (players[player].powerLeft + 0.0) / playerMaxPower;
            }
        }

        /// <summary>
        /// Get the total percent of the screen the given player filled.
        /// </summary>
        /// <param name="player">The player that the percent was requested.</param>
        /// <returns>The percent.</returns>
        public double GetTotalPercentage(int player)
        {
            return blobField.GetTeamPercentage(player);
        }

        /// <summary>
        /// Get the percent of the screen the given blob of the given player filled.
        /// Returns -1 if that player doesnt have that many blobs.
        /// </summary>
        /// <param name="player">The player that the percent was requested.</param>
        /// <param name="blob">The blob that the percent was requested.</param>
        /// <returns>The percent.</returns>
        public double GetBlobPercentage(int player, int blob)
        {
            return blobField.GetBlobPercentage(player, blob);
        }

        /// <summary>
        /// Returns the color of the given player.
        /// </summary>
        /// <param name="player">Player of the color your requesting.</param>
        /// <returns>The color.</returns>
        public Color GetColor(int player)
        {
            return players[player].color;
        }

        #endregion
        
        #region Private Methods

        /// <summary>
        /// If the point is safe and wasn't used for a previous blob.
        /// </summary>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        /// <returns>Returns whether the point is safe to use.</returns>
        private bool SafePoint(int x, int y)
        {
            foreach (Player p in players)
            {
                foreach(Selection s in p.blobs)
                {
                    if (s.x == x && s.y == y)
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Whether their is a blob currently being selected.
        /// </summary>
        public bool InSelection
        {
            private set { inSelection = value; }
            get { return inSelection; }
        }

        #endregion

        #region Private Types

        /// <summary>
        /// Enum of all the modes the game can be in.
        /// </summary>
        private enum Modes
        {
            SplashScreen,
            MainMenu,
            OptionsMenu,
            RulesMenu,
            MapSelection,
            BlobSelection,
            InGame,
            PostGame,
            Score
        };

        /// <summary>
        /// Used to represent each blob selection made by each player.
        /// Keeps the location, size, and Radar Circle to draw.
        /// </summary>
        private struct Selection
        {
            public int x;
            public int y;
            public int size;
            public RadarCircle rc;
        }

        /// <summary>
        /// Mini Class to represent each player.
        /// </summary>
        private class Player
        {
            public string name;
            public int team;
            public int powerLeft;
            public int score;
            public Color color;
            public List<Selection> blobs;
            public bool active;

            public Player(string name,Color color, int team, int power)
            {
                this.name = name;
                this.color = color;
                this.team = team;

                powerLeft = power;
                score = 0;
                blobs = new List<Selection>();

                active = true;
            }
        }

        #endregion
    }
}
